package xyz.xtt.asset.service.impl;

import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static xyz.xtt.common.asset.AssertUtils.isFalse;
import static xyz.xtt.common.asset.AssertUtils.isNull;
import static xyz.xtt.common.asset.AssertUtils.isTrue;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.google.common.collect.Lists;
import com.google.common.primitives.Longs;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import xyz.xtt.asset.api.enums.DealStateEnum;
import xyz.xtt.asset.constants.AssetCache;
import xyz.xtt.asset.constants.AssetConstants;
import xyz.xtt.asset.constants.ErrorMessage;
import xyz.xtt.asset.entity.TblArtAmberCard;
import xyz.xtt.asset.entity.TblArtAmberOrder;
import xyz.xtt.asset.entity.TblAssetTransaction;
import xyz.xtt.asset.request.MystryBoxMessage;
import xyz.xtt.asset.service.IInnerService;
import xyz.xtt.asset.service.IMystryBoxService;
import xyz.xtt.asset.service.INftService;
import xyz.xtt.asset.service.ITblArtAmberCardService;
import xyz.xtt.asset.service.ITblArtAmberOrderService;
import xyz.xtt.asset.service.ITblAssetTransactionService;
import xyz.xtt.asset.utils.AESUtil;
import xyz.xtt.asset.utils.RSAUtil;
import xyz.xtt.asset.vo.MystryBoxHttpResponse;
import xyz.xtt.asset.vo.MystryBoxResponse;
import xyz.xtt.asset.vo.TransferFromResult;
import xyz.xtt.common.starter.cache.CacheManager;
import xyz.xtt.user.api.enums.UserStatus;
import xyz.xtt.user.client.response.UserInfoResponse;

@RefreshScope
@Slf4j
@Service
public class MystryBoxServiceImpl implements IMystryBoxService {
	@Autowired
	private CacheManager cacheManager;

	@Value("${activity.mystrybox.accountNo}")
	private Long mystryBoxAccountNo;

	@Value("${activity.mystrybox.assetId}")
	private Long mystryBoxAssetId;

	@Value("${activity.mystrybox.publickey}")
	private String mystryBoxPublicKey;

	@Value("${activity.mystrybox.secret}")
	private String mystryBoxSecret;

	@Value("${activity.mystrybox.configId}")
	private String mystryBoxConfigId;

	@Value("${activity.mystrybox.openUrl}")
	private String mystryBoxOpenUrl;
	@Autowired
	private IInnerService iInnerService;
	@Autowired
	private ITblArtAmberCardService iTblArtAmberCardService;
	@Autowired
	private ITblArtAmberOrderService iTblArtAmberOrderService;
	@Autowired
	private ITblAssetTransactionService iTblAssetTransactionService;
	@Autowired
	private INftService nftService;

	@Transactional(rollbackFor = Exception.class)
	@Override
	public Boolean sendOpen(TblAssetTransaction transaction) {
		log.info("Handled event:{}", JSONUtil.toJsonStr(transaction));
		Long assetId = transaction.getAssetId();
		if (!Objects.equals(assetId, mystryBoxAssetId)) {
			return Boolean.FALSE;
		}
		sendOpen(transaction, "asset", false);
		return Boolean.TRUE;
	}

	@Override
	public Boolean sendOpen(TblAssetTransaction transaction, String bizType, Boolean append) {
		isNull(transaction, ErrorMessage.TRANSATION_NOT_EXIST);
		String totalAmount = transaction.getAmount().multiply(transaction.getPrice()).stripTrailingZeros().toPlainString();
		sendOpen(transaction.getOrderId(), transaction.getAssetId(), totalAmount, Longs.tryParse(transaction.getBuyerUserId()), bizType, append);
		return Boolean.TRUE;
	}

	@Override
	public void sendOpen(Long transactionId, Long assetId, String totalAmount, Long appUid, String bizType, Boolean append) {
		UserInfoResponse userInfo = iInnerService.userInfo(appUid);
		isNull(userInfo, ErrorMessage.USER_NOT_EXIST, () -> log
				.info("transactionId:{},totalAmount:{},bizType:{},uid:{} user is null", transactionId, totalAmount, bizType, appUid));
		isFalse(userInfo.checkStatus(), ErrorMessage.USER_STATUS_EX, () -> log
				.info("transactionId:{},totalAmount:{},bizType:{},user:{} status ex", transactionId, totalAmount, bizType, JSONUtil.toJsonStr(userInfo)));
		sendOpen(transactionId, assetId, totalAmount, userInfo, bizType, append);
	}

	@Override
	public void sendOpen(Long transactionId, Long assetId, String totalAmount, UserInfoResponse info, String bizType, Boolean append) {
		String phone = info.getPhoneBk();
		if (StringUtils.isBlank(phone) && StringUtils.isNoneBlank(info.getPhone())) {
			phone = AESUtil.decrypt(info.getPhone());
		}
		// 补发的情况下不检查订单
		if (Objects.equals(append, Boolean.FALSE)) {
			TblArtAmberOrder artAmberOrder = iTblArtAmberOrderService.info(transactionId);
			if (Objects.nonNull(artAmberOrder)) {
				log.info("sendOpen artAmberOrder:{}", JSONUtil.toJsonStr(artAmberOrder));
				return;
			}
		}

		phone = phone(phone);
		String nickname = info.getNickname();
		String orderNumber = String.valueOf(transactionId);
		MystryBoxHttpResponse response = sendOpen(info.getAppUserId(), mystryBoxConfigId, phone, nickname, orderNumber, totalAmount);
		log.info("sendOpen response:{}", JSONUtil.toJsonStr(response));
		Long appUserId = info.getAppUserId();
		TblArtAmberOrder order = TblArtAmberOrder
				.builder()
				.id(IdUtil.getSnowflakeNextId())
				.appUserId(appUserId)
				.assetId(assetId)
				.phone(phone)
				.bizType(bizType)
				.userName(nickname)
				.configId(mystryBoxConfigId)
				.orderNumber(orderNumber)
				.totalAmount(totalAmount)
				.crt(LocalDateTime.now())
				.httpStatus(response.getStatusCode())
				.thirdRequestStart(LocalDateTime.now())
				.thirdRequestStatus(response.getStatusCode())
				.thirdRequestResponse(response.getResponseText())
				.cardSize(0)
				.build();
		if (Objects.equals(HttpStatus.SC_OK, response.getStatusCode())) {
			String responseText = response.getResponseText();
			MystryBoxResponse bean = JSONUtil.toBean(responseText, MystryBoxResponse.class);
			isNull(bean, ErrorMessage.UNKNOWN_ERROR);
			order.setThirdRequestStatus(bean.getCode());
			List<TblArtAmberCard> artAmberCards = Optional
					.ofNullable(bean)
					.map(MystryBoxResponse::getData)
					.orElse(Lists.newArrayList())
					.stream()
					.map(x -> x.convert(transactionId, appUserId))
					.collect(toList());
			order.setCardSize(artAmberCards.size());
			if (CollectionUtil.isNotEmpty(artAmberCards)) {
				boolean saveBatchArtAmberCards = iTblArtAmberCardService.saveBatch(artAmberCards);
				if (saveBatchArtAmberCards) {
					cacheManager.delete(AssetCache.QUERY_ART_CARD_WITH_UID, String.valueOf(appUserId));
				}
				log.info("saveBatchArtAmberCard result: {}", saveBatchArtAmberCards);
			}
		}
		// 不是补发才插入订单
		if (Objects.equals(append, Boolean.FALSE)) {
			boolean saveArtAmberOrder = iTblArtAmberOrderService.save(order);
			if (saveArtAmberOrder) {
				cacheManager.delete(AssetCache.TBL_ART_AMBER_ORDER, String.valueOf(transactionId));
				log.info("saveArtAmberOrder respone succ result: {}", saveArtAmberOrder);
			}
		}
	}

	public MystryBoxHttpResponse sendOpen(Long uid, String configId, String phonenumber, String userName, String orderNumber, String totalAmount) {
		List<String> params = Arrays.asList(phonenumber, userName, orderNumber, totalAmount);
		boolean anyMatch = params.stream().anyMatch(StringUtils::isBlank);
		if (anyMatch) {
			String parmStr = params.stream().collect(joining(","));
			return MystryBoxHttpResponse.builder().statusCode(0).responseText("参数错误,未发起请求: " + parmStr).build();
		}
		if (phonenumber.length() != 11) {
			log.info("phone length err uid:{} phonenumber: {}", uid, phonenumber);
			return MystryBoxHttpResponse.builder().statusCode(0).responseText("手机号长度错误 uid:" + uid + " phone:" + phonenumber).build();
		}
		Map<String, Object> bizParams = new HashMap<>();
		// 业务参数
		// 手机号 必传
		bizParams.put("phonenumber", phonenumber);
		// 用户昵称 必传
		bizParams.put("userName", userName);
		// 盲盒配置ID 必传 测试环境使用：676e423f3e021d3ce6381b4b
		bizParams.put("configId", configId);
		// 订单号 必传
		bizParams.put("orderNumber", orderNumber);
		// 订单金额 必传
		bizParams.put("totalAmount", totalAmount);
		// 当前时间和签名
		bizParams.put("signtime", System.currentTimeMillis() + "");
		bizParams.put("sign", RSAUtil.generateSign(mystryBoxSecret, bizParams));
		int statusCode = 0;
		String responseBody = StringUtils.EMPTY;
		try {
			// 数据加密
			String encodedData = RSAUtil.publicEncrypt(JSONUtil.toJsonStr(bizParams), RSAUtil.getPublicKey(mystryBoxPublicKey));
			Map<String, Object> map = new HashMap<>();
			map.put("data", encodedData);
			String jsonStr = JSONUtil.toJsonStr(map);
			HttpRequest request = HttpRequest
					.newBuilder()
					.uri(URI.create(mystryBoxOpenUrl))
					.header("appname", "collector")
					.header("Content-Type", "application/json")
					.timeout(Duration.ofSeconds(15))
					.POST(HttpRequest.BodyPublishers.ofString(jsonStr, StandardCharsets.UTF_8))
					.build();
			HttpResponse<String> send = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
			statusCode = send.statusCode();
			responseBody = send.body();
			return MystryBoxHttpResponse.builder().statusCode(statusCode).responseText(responseBody).build();
		} catch (IOException | InterruptedException e) {
			log.info("MystryBoxHttp IOException | InterruptedException ex: {}", e.getMessage(), e);
		} catch (Exception e) {
			log.info("MystryBoxHttp Exception ex: {}", e.getMessage(), e);
		}
		return MystryBoxHttpResponse.builder().statusCode(statusCode).responseText(responseBody).build();
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public Boolean sendMystryBox(MystryBoxMessage message) {
		Long ttUserId = message.getTtUserId();
		String orderNo = message.getOrderNo();
		isNull(ttUserId, ErrorMessage.TARGET_USER_EXIST, () -> log.info("sendMystryBox message uid is null msg:{}", JSONUtil.toJsonStr(message)));
		UserInfoResponse userInfo = iInnerService.userInfo(ttUserId);
		isNull(userInfo, ErrorMessage.TARGET_USER_EXIST, () -> log.info("target user: {} is not exist msg:{}", ttUserId, JSONUtil.toJsonStr(message)));
		if (!Objects.equals(UserStatus.NORMAL.getCode(), userInfo.getStatus())) {
			log.info("sendMystryBox user Logoff uid:{},orderNo:{},msg:{}", ttUserId, orderNo, JSONUtil.toJsonStr(message));
			return Boolean.FALSE;
		}
		isTrue(StringUtils.isBlank(orderNo), ErrorMessage.ARGUMENT_NOT_VALID, () -> log.info("orderNo is blank msg:{}", JSONUtil.toJsonStr(message)));
		TblAssetTransaction transaction = this.iTblAssetTransactionService.infoWithOrderNo(orderNo);
		if (Objects.nonNull(transaction)) {
			log.info("sendMystryBox user Logoff uid:{},orderNo:{},msg:{} received", ttUserId, orderNo, JSONUtil.toJsonStr(message));
			return Boolean.FALSE;
		}
		TransferFromResult transferFrom = nftService.transferFromWithActivity(mystryBoxAccountNo, ttUserId, orderNo, 1, 0L, AssetConstants.CMP);
		isNull(transferFrom, ErrorMessage.UNKNOWN_ERROR, () -> log
				.info("sendMystryBox transferFrom is null fail accountNo: {}, msg:{}", mystryBoxAccountNo, JSONUtil.toJsonStr(message)));
		isFalse(transferFrom.isOk(), ErrorMessage.UNKNOWN_ERROR, () -> log
				.info("sendMystryBox transferFrom result is false fail transferFrom {}, msg:{}", transferFrom, JSONUtil.toJsonStr(message)));
		sendOpen(transferFrom.getTransaction(), AssetConstants.CMP, false);
		return Boolean.TRUE;
	}

	private String phone(String phone) {
		String rphone = phone;
		if (phone.startsWith("86")) {
			rphone = phone.substring(2);
		}
		if (phone.startsWith("086")) {
			rphone = phone.substring(3);
		}
		return rphone;
	}

	@Override
	public Boolean sendMystryCard(Long transactionId) {
		TblArtAmberOrder artAmberOrder = iTblArtAmberOrderService.infoException(transactionId);
		if (Objects.isNull(artAmberOrder)) {
			log.info("sendMystryCard artAmberOrder isNull transactionId:{}", transactionId);
			return Boolean.FALSE;
		}
		Integer httpStatus = artAmberOrder.getThirdRequestStatus();
		if (!Objects.equals(500, httpStatus.intValue())) {
			log.info("sendMystryCard httpStatus not equal 500 transactionId:{} status:{}", transactionId, httpStatus);
			return Boolean.FALSE;
		}
		TblAssetTransaction transaction = this.iTblAssetTransactionService.getById(Longs.tryParse(artAmberOrder.getOrderNumber()));
		if (Objects.isNull(transaction)) {
			log.info("sendMystryCard transaction isNull transactionId:{}", transactionId);
			return Boolean.FALSE;
		}
		if (!Objects.equals(transaction.getDealState(), DealStateEnum.PAID.code())) {
			log.info("sendMystryCard transaction payState ex transactionId:{},paySate:{}", transactionId, transaction.getPayState());
			return Boolean.FALSE;
		}
		Long queryCardSize = this.iTblArtAmberCardService.queryCardSize(Longs.tryParse(artAmberOrder.getOrderNumber()));
		if (Objects.nonNull(queryCardSize) && queryCardSize.longValue() > 0) {
			log.info("sendMystryCard transaction transactionId:{},queryCardSize:{}", transactionId, queryCardSize);
			return Boolean.FALSE;
		}
		Boolean sendOpen = sendOpen(transaction, transaction.getBizType(), true);
		log.info("sendMystryCard sendOpen result:{} transactionId:{},paySate:{}", sendOpen, transactionId, transaction.getPayState());
		Boolean updateArtAmberOrderStatus = this.iTblArtAmberOrderService.updateArtAmberOrderStatusWithId(artAmberOrder.getId());
		isFalse(updateArtAmberOrderStatus, ErrorMessage.SEND_CARD_EX);
		return Boolean.TRUE;
	}
}
